package br.edu.ufcg.dsc.psoo.mymoney.controller.implementation;

import java.io.FileNotFoundException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

import br.edu.ufcg.dsc.psoo.mymoney.controller.MovementController;
import br.edu.ufcg.dsc.psoo.mymoney.controller.Validator;
import br.edu.ufcg.dsc.psoo.mymoney.controller.exceptions.AccountNotFoundException;
import br.edu.ufcg.dsc.psoo.mymoney.controller.exceptions.GroupNotFoundException;
import br.edu.ufcg.dsc.psoo.mymoney.controller.exceptions.InvalidFileException;
import br.edu.ufcg.dsc.psoo.mymoney.controller.exceptions.InvalidMovementException;
import br.edu.ufcg.dsc.psoo.mymoney.controller.exceptions.InvalidUserException;
import br.edu.ufcg.dsc.psoo.mymoney.controller.exceptions.TypeNotFoundException;
import br.edu.ufcg.dsc.psoo.mymoney.controller.io.ReadWithScanner;
import br.edu.ufcg.dsc.psoo.mymoney.model.bean.Account;
import br.edu.ufcg.dsc.psoo.mymoney.model.bean.Group;
import br.edu.ufcg.dsc.psoo.mymoney.model.bean.Movement;
import br.edu.ufcg.dsc.psoo.mymoney.model.bean.Type;
import br.edu.ufcg.dsc.psoo.mymoney.model.bean.User;
import br.edu.ufcg.dsc.psoo.mymoney.model.dao.AccountDAO;
import br.edu.ufcg.dsc.psoo.mymoney.model.dao.GroupDAO;
import br.edu.ufcg.dsc.psoo.mymoney.model.dao.MovementDAO;
import br.edu.ufcg.dsc.psoo.mymoney.model.dao.TypeDAO;
import br.edu.ufcg.dsc.psoo.mymoney.model.dao.UserDAO;
import br.edu.ufcg.dsc.psoo.mymoney.model.hibernate.HibernateUtil;

/**
 * Controller respons�vel pela valida��o e opera��es envolvendo movimenta��es
 * financeiras
 * 
 * @author leonardo
 * 
 */
public class MovementControllerImpl implements MovementController {

	private MovementDAO movementDAO;
	private UserDAO userDAO;
	private GroupDAO groupDAO;
	private AccountDAO accountDAO;
	private TypeDAO typeDAO;

	private SessionFactory sf = HibernateUtil.getSessionFactory();
	private Session session;

	private Validator validator = new Validator();

	/**
	 * Construtor padrao recebendo o DAO de Movement via Injection (Mentawai)
	 * 
	 * @param groupDAO
	 */
	public MovementControllerImpl(MovementDAO movementDAO, UserDAO userDAO,
			GroupDAO groupDAO, AccountDAO accountDAO, TypeDAO typeDAO) {
		this.movementDAO = movementDAO;
		this.accountDAO = accountDAO;
		this.userDAO = userDAO;
		this.typeDAO = typeDAO;
		this.groupDAO = groupDAO;
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#closeSession()
	 */
	public void closeSession() {
		if (session.isOpen()) {
			session.close();
			userDAO.setSession(session);
			typeDAO.setSession(session);
			groupDAO.setSession(session);
			accountDAO.setSession(session);
			movementDAO.setSession(session);
		}
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#create(java.lang.Long, java.lang.Long, java.lang.Long, java.lang.Long, java.lang.String, java.lang.String, java.lang.String)
	 */
	public Movement create(Long userId, Long accountId, Long typeId,
			Long groupId, String date, String description, String value)
			throws InvalidMovementException, InvalidUserException,
			ParseException {
		openSession();
		User user = new User();
		user.setId(userId);

		Type type = new Type();
		type.setId(typeId);
		type.setUser(user);

		Account account = new Account();
		account.setId(accountId);
		account.setUser(user);
		
		Group group = null;
		
		if (groupId!=null) {
			group = new Group();
			group.setId(groupId);
		}

		if (userId == null || userDAO.load(user.getId()) == null) {
			throw new InvalidUserException("Usuario invalido!");
		} else {
				if (typeId == null/* || typeDAO.load(type)==null */) {
					throw new InvalidMovementException("Tipo invalido!");
				} else {
					if (accountId == null/* || accountDAO.load(account)==null */) {
						throw new InvalidMovementException("Conta invalida!");
					}

					else {
						// descriçao maior que o permitido
						if (!validator.sizeValidator(description, 0, 30)) {
							throw new InvalidMovementException(
									"Descricao invalida!");
						} else {
							// data inválida
							if (!validator.dateValidator(date)) {
								throw new InvalidMovementException(
										"Data invalida!");
							} else {
								if (!validator.valueValidator(value)) {
									throw new InvalidMovementException(
											"Valor invalido!");
								}
							}
						}
					}
				}

			
		}

		DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		Date convertedDate = (java.util.Date) formatter.parse(date);

		float convertedValue = Float.parseFloat(value);

		Movement newMovement = new Movement(description, convertedDate,
				convertedValue, type, group, account);

		Movement createdMovement = movementDAO.create(newMovement);
		closeSession();
		return createdMovement;
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#delete(java.lang.Long, java.lang.Long)
	 */
	public void delete(Long id, Long accountId) throws InvalidMovementException {
		openSession();
		Movement movement = movementDAO.load(id);

		if (movement == null)
			throw new InvalidMovementException("Movimentacao nao encontrada!");
		else {

			if (!(movement.getAccount().getId().equals(accountId))) {
				throw new InvalidMovementException(
						"You don't have permission to delete this movement!");
			} else {
				movementDAO.delete(movement);
			}
		}
		closeSession();

	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#importFile(java.lang.Long, java.lang.String)
	 */
	public List<Movement> importFile(Long userId, String fileName)
			throws FileNotFoundException, InvalidFileException,
			GroupNotFoundException, TypeNotFoundException,
			AccountNotFoundException, InvalidMovementException,
			InvalidUserException, ParseException {

		List<ArrayList<String>> fileData = new ArrayList<ArrayList<String>>();

		List<Movement> result = new ArrayList<Movement>();

		ReadWithScanner parser = new ReadWithScanner(fileName);
		fileData = parser.processLineByLine();

		// TODO esse bloco � atomico, realizar dentro de uma transacao do
		// hibernate
		for (ArrayList<String> movementString : fileData) {
			openSession();
			Account acc = accountDAO.findByName(movementString.get(0));

			if (acc == null)
				throw new AccountNotFoundException("Conta \""
						+ movementString.get(0)
						+ "\" especificada no arquivo nao encontrada!");

			Type type = typeDAO.findByName(movementString.get(1));

			if (type == null)
				throw new TypeNotFoundException("Tipo \""
						+ movementString.get(1)
						+ "\" especificado no arquivo nao encontrado!");
			
			Group group = null;
			//TODO fazer com criteria
			List<Group> groups = groupDAO.findByName(movementString.get(2));
			
			for (Group g : groups) {
				if (g.getAccount().equals(acc))
					group = g;
			}

			if (group == null)
				throw new GroupNotFoundException("Grupo \""
						+ movementString.get(2)
						+ "\" especificado no arquivo nao encontrado!");

			Movement createdMovement = create(userId, acc.getId(),
					type.getId(), group.getId(), movementString.get(3),
					movementString.get(4), movementString.get(5));

			result.add(createdMovement);

			closeSession();
		}

		return result;

	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#notify(java.lang.String, java.lang.String)
	 */
	@SuppressWarnings("deprecation")
	public List<Movement> notify(String account, String initialDate)
			throws InvalidMovementException {
		openSession();
		DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		Date initial, finalD;

		// data final: intervalo de dias a frente
		try {
			initial = (java.util.Date) formatter.parse(initialDate);
		} catch (ParseException e) {
			throw new InvalidMovementException("Data invalida!");
		}

		// TODO retirar m�todos deprecated
		int year = initial.getYear() + 1900;
		int month = initial.getMonth();
		int day = initial.getDate();

		Calendar reference = new GregorianCalendar(year, month, day);

		Calendar startDate = (Calendar) reference.clone();
		Calendar endDate = (Calendar) reference.clone();

		endDate.set(Calendar.DAY_OF_YEAR, startDate.get(Calendar.DAY_OF_YEAR) + 6);
		finalD = endDate.getTime();
		List<Movement> list = movementDAO.find("date", account, "Debito", null,
				initial, finalD, null);
		closeSession();
		return list;

	}
	//TODO javadoc
	public Date stringToDate(String dateString) throws InvalidMovementException {
		DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		Date date;

		// data final: intervalo de dias a frente
		try {
			date = (java.util.Date) formatter.parse(dateString);
		} catch (ParseException e) {
			throw new InvalidMovementException("Data invalida!");
		}
		return date;
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#openSession()
	 */
	public void openSession() {
		session = sf.openSession();
		userDAO.setSession(session);
		typeDAO.setSession(session);
		groupDAO.setSession(session);
		accountDAO.setSession(session);
		movementDAO.setSession(session);
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#searchMovement(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
	 */
	public List<Movement> searchMovement(String order, String account,
			String type, String group, String initialDate, String finalDate,
			String description) throws InvalidMovementException {
		openSession();
		DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		Date initial = null, finalD = null;
		try {
			if (initialDate!=null) initial = (java.util.Date) formatter.parse(initialDate);
		} catch (ParseException e) {
			throw new InvalidMovementException("Data inicial invalida!");
		}

		try {
			if (finalDate!=null) finalD = (java.util.Date) formatter.parse(finalDate);
		} catch (ParseException e) {
			throw new InvalidMovementException("Data final invalida!");
		}

		List<Movement> list = movementDAO.find(order, account, type, group,
				initial, finalD, description);
		closeSession();
		return list;
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#setSessionFactory(org.hibernate.SessionFactory)
	 */
	public void setSessionFactory(SessionFactory sf) {
		this.sf = sf;
	}

	/* (non-Javadoc)
	 * @see br.edu.ufcg.dsc.psoo.mymoney.controller.MovementControllerIn#update(java.lang.Long, java.lang.Long, java.lang.Long, java.lang.Long, java.lang.Long, java.lang.String, java.lang.String, java.lang.String)
	 */
	public Movement update(Long id, Long userId, Long accountId, Long typeId,
			Long groupId, String date, String description, String value)
			throws InvalidMovementException, InvalidUserException,
			ParseException {
		openSession();
		Movement m = new Movement();
		m.setId(id);

		User user = new User();
		user.setId(userId);

		Type type = new Type();
		type.setId(typeId);
		type.setUser(user);

		Account account = new Account();
		account.setId(accountId);
		account.setUser(user);

		Group group = null;
		
		if (groupId!=null) {
			group = new Group();
			group.setId(groupId);
		}

		if (userId == null || userDAO.load(user.getId()) == null) {
			throw new InvalidUserException("Usuario invalido!");
		} else {
				if (typeId == null/* || typeDAO.load(type)==null */) {
					throw new InvalidMovementException("Tipo invalido!");
				} else {
					if (accountId == null/* || accountDAO.load(account)==null */) {
						throw new InvalidMovementException("Conta invalida!");
					}

					else {
						// if (movementDAO.load(m)==null) {
						// throw new
						// InvalidMovementException("Invalid movement for update!");
						// }
						// else {
						// descriçao maior que o permitido
						if (!validator.sizeValidator(description, 0, 30)) {
							throw new InvalidMovementException(
									"Descricao invalida!");
						} else {
							// data inválida
							if (!validator.dateValidator(date)) {
								throw new InvalidMovementException(
										"Data invalida!");
							} else {
								if (!validator.valueValidator(value)) {
									throw new InvalidMovementException(
											"Valor invalido!");
								}
							}
						}
						// }
					
				}

			}
		}
		DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		Date convertedDate = (java.util.Date) formatter.parse(date);

		float convertedValue = Float.parseFloat(value);

		Movement newMovement = new Movement(description, convertedDate,
				convertedValue, type, group, account);
	
		newMovement.setId(id);

		Movement updatedMovement = movementDAO.update(newMovement);
		closeSession();
		return updatedMovement;
	}
	
	public List<Movement> find(String order, String accountName, String typeName, String groupName, Date initialDate, Date finalDate, String description) {
		openSession();
		List<Movement> result = movementDAO.find(order, accountName, typeName, groupName, initialDate, finalDate, description);
		closeSession();
		return result;
	}
	
	public Movement loadById(Long id) {
		openSession();
		Movement movement = movementDAO.load(id);
		closeSession();
		return movement;
	}
}